プレゼンタイマー (pokutsuna)
font awesomeが入っている<link>を取得
code:js
await navigator.clipboard.writeText(link);
PCなら開発ツールからコピペできるが、smartphoneだとできないので、コピー用scriptを適当に書いた
変更点
JSXにした
TypeScriptにした
shadow Rootにattachして、CSSをカプセル化した
閉じるボタンを付けた
code:app.tsx
/** @jsx h */
/** @jsxFrag Fragment */
import {
h, Fragment, render
import {
useReducer, useEffect,
import {
lightFormat
const app = document.createElement("div");
app.attachShadow({mode: "open"});
const cleanup = () => app.remove();
document.body.append(app);
type Action = {
type: "reset" | "tick" | "pause" | "start";
} | {
type: "buttons";
show: boolean;
};
interface State {
now: Date;
startAt: Date;
pause: boolean;
showButtons: boolean;
}
function reducer(state: State, action: Action): State {
switch (action.type) {
case "reset":
return {
...state,
startAt: new Date(),
now: new Date(),
pause: false,
showButtons: false
};
case "pause":
return {
...state,
pause: true
};
case "start":
const ajusted = new Date() - (state.now - state.startAt);
return {
...state,
startAt: ajusted,
now: new Date(),
pause: false,
};
case "tick":
return state.pause ? state : {
...state,
now: new Date()
};
case "buttons":
return {
...state,
showButtons: action.show
};
}
}
function formatTimer(state: State) {
const diff = state.now - state.startAt;
const offset = new Date().getTimezoneOffset() * 60 * 1000;
const fmt = diff >= 60 * 60 * 1000 ? 'HH:mm:ss' : 'mm:ss';
return lightFormat(diff + offset, fmt) ;
}
type TimerButtonsProps = {
state: State;
dispatch: (action: Action) => void;
};
const TimerButtons = ({
state: {
showButtons,
pause,
},
dispatch,
}: TimerButtonsProps) => (showButtons || pause) && (
<span className="timer-buttons">
{ pause ?
<>
<i className="fas fa-play-circle"
onClick={() => dispatch({ type: "start" })} />
<i className="fas fa-minus-circle"
onClick={() => dispatch({ type: "reset" })} />
</> :
<i className="fas fa-pause-circle"
onClick={() => dispatch({ type: "pause" })} />
}
</span>
);
const PresentationTimer = () => {
startAt: new Date(),
now: new Date(),
pause: false,
showButtons: false,
});
useEffect(() => {
const tid = setInterval(
() => dispatch({ type: "tick" }),
500
);
return () => clearInterval(tid);
return (
<ins className="presentation-timer"
onMouseEnter={() => dispatch({ type: "buttons", show: true })}
onMouseLeave={() => dispatch({ type: "buttons", show: false })}>
<button onClick={cleanup}>x</button>
<span>{formatTimer(state)}</span>
<TimerButtons state={state} dispatch={dispatch} />
</ins>
);
};
const App = () => <>
<link
rel="stylesheet"
/>
<style>{`
.presentation-timer {
position: fixed;
left: 0;
bottom: 0;
z-index: 9999;
text-decoration: none;
padding: 8px;
font-size: 32px;
font-weight: 800;
display: flex;
frex-direction: row;
justify-content: center;
align-items: center;
}
.presentation-timer .timer-buttons {
margin-left: 0.2em;
display: flex;
}
.presentation-timer .timer-buttons i {
font-size: 90%;
margin: 0 2px;
font-style: normal;
font-family: "Font Awesome 5 Free";
}
`}</style>
<PresentationTimer />
</>;
// start
render(<App />, app.shadowRoot);